/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.gwt.widgets.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.CellPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;

/**
 * A simple aggergator and applier of CSS styles for a CellPanel.
 *  Used primarily by the AxisPanel but maybe used externally as well.
 * <br><br>
 * <b>Sample Usage:</b>
 * <code><pre>
 * new AxisPanelStyle(myCellPanel).border(1).size(800,600).css("search");
 * </code></pre>
 * 
 * @see AxisPanel
 * 
 * @author Sony Mathew
 */
public class AxisPanelStyle {

    /**
     * Marker for owners that hold a AxisPanelStyle object.
     *  
     * @author Sony Mathew
     */
    public interface Owner {
        /**
         * Should never return null.
         */
        public AxisPanelStyle getAxisPanelStyle();        
    }
    
    /* zero/empty value indicates no value was set */
    private String id = "";
    private String cssClass = "";
    private int borderWidth = 0;
    private int pixelWidth = 0;
    private double pixelWidthPercent = 0.0;
    private String cssWidth = "";
    private int pixelHeight = 0;
    private double pixelHeightPercent = 0.0;
    private String cssHeight = "";
    private int pixelSpacing = 0;
    private int alignX = 0;
    private int alignY = 0;

    private final CellPanel owner;
        
    /**
     * Create an AxisPanelStyle around the given CellPanel 
     *  either a VerticalPanel or HorizontalPanel.
     * 
     * <br>
     * Given owner doesn't need to implement AxisPanelStyle.Owner but if you want 
     *  relative style values to work a parent (or ancenstor) needs to be a 
     *  AxisPanelStyle.Owner
     *   
     * @param aOwner : The CellPanel to which all Styles are applied.
     *  All style changes are applied immediately to the given owner.
     *  
     */
    public AxisPanelStyle(CellPanel aOwner) {
        this.owner = aOwner;        
    }
    
    /**
     * @return CellPanel : The owner of this Style
     * 
     */
    public CellPanel getOwner() {
        return owner;
    }

    /**
     * @return : The element id of owner. This is a computed id prefixed with
     *   parent's id + "-".
     * 
     * @see #id(String)
     * 
     */
    public String getID() {
        return id;
    }
    
    /**
     * @return : CSS class name of owner.
     * @see #css(String)
     * 
     */
    public String getCSSClass() {
        return cssClass;
    }
    
    /**
     * @return int : -1, 0, 1 for horizontal left, center, right
     * @see #align(int,int)
     */
    public int getAlignX() {
        return alignX;
    }

    /**
     * @return int : 1, 0, -1 for vertical top, middle, bottom.
     * @see #align(int,int)
     */
    public int getAlignY() {
        return alignY;
    }
    
    /**
     * @see #height(String)
     */
    public String getCSSHeight() {
        return cssHeight;
    }

    /**
     * @see #width(String)
     */
    public String getCSSWidth() {
        return cssWidth;
    }

    /**
     * @see #border(int)
     */
    public int getBorderWidth() {
        return borderWidth;
    }
    
    /**
     * @return int: the exact pixel height (positive) 
     *      or a negative pixel difference relative to the owner's parent.
     * 
     * @see #size(int, int)
     */
    public int getPixelHeight() {
        return pixelHeight;
    }

    /**
     * @return double: pixel height percent relative to the parent's 
     *  pixel height
     * 
     * @see #size(double, double)
     * author Sony Mathew
     */
    public double getPixelHeightPercent() {
        return pixelHeightPercent;
    }

    /**
     * @return int: the exact pixel width (positive) 
     *      or a negative pixel difference relative to the owner's parent.
     * 
     * @see #size(int, int)
     */
    public int getPixelWidth() {
        return pixelWidth;
    }


    /**
     * @return double: pixel width percent relative to the parent's 
     *  pixel width.
     *  
     * @see #size(double, double)
     */
    public double getPixelWidthPercent() {
        return pixelWidthPercent;
    }

    /**
     * Spacing in pixels placed around the children of the owner panel.
     *   The spacing pixel amount is placed on all 4 sides of each child
     *      contained in the owner.
     * 
     * @see #space(int)
     */
    public int getPixelSpacing() {
        return pixelSpacing;
    }

    /**
     * @return AxisPanelStyle: The first parent with a style in the parent heirarchy
     *  if one exists otherwise null.
     */
    public AxisPanelStyle getParentStyle() {
        if (owner instanceof AxisPanel) {
            return null; //don't check any further than an AxisPanel
        }
        Widget parent = owner.getParent();
        while (parent != null) {
            if (parent instanceof AxisPanelStyle.Owner) {
                AxisPanelStyle parentStyle = ((AxisPanelStyle.Owner)parent).getAxisPanelStyle();
                return parentStyle;
            }
            parent = parent.getParent();
        }
        return null;
    }
    
    /**
     * Set the id of the owner.
     * The given id is prefixed with parent id if exists.
     * Will use first parent id that is non-empty as the prefix.
     * <br>
     * E.g if parent id is "search" and the given id "results"
     *      the final id will be "search-results".
     *      
     * author Sony Mathew
     */
    public AxisPanelStyle id(String anID) {
        this.id = "";
        AxisPanelStyle parentStyle = getParentStyle();
        while (parentStyle != null) {
            if (parentStyle.id.length()>0) {
                this.id = parentStyle.id + "-";
                break;
            }
            parentStyle = parentStyle.getParentStyle();
        }        
        this.id += anID;        
        //GWT.log("assigned id [" + this.id + "]", null);
        
        DOM.setElementAttribute(owner.getElement(), "id", this.id);
        return this;
    }
    
    /**
     * Set an arbitary style attribute.
     * 
     * author Sony Mathew
     */
    public AxisPanelStyle style(String styleAttrib, String styleValue) {
        DOM.setStyleAttribute(owner.getElement(), styleAttrib, styleValue);
        return this;
    }
    
    /**
     * Add the CSS class name of this style.
     */
    public AxisPanelStyle css(String aCSSClass) {
        this.cssClass += " " + aCSSClass;        
        owner.addStyleName(aCSSClass);
        return this;
    }

    /**
     * Set the border width.
     */
    public AxisPanelStyle border(int aBorderWidth) {
        this.borderWidth = aBorderWidth;
        owner.setBorderWidth(this.borderWidth);
        return this;
    }
    
    /**
     * Provide the exact pixel width/height to be used 
     * or 
     * a negative number to subtract from the parent style's width/height
     */
    public AxisPanelStyle size(int aPixelWidth, int aPixelHeight) {
        
        if (aPixelWidth >= 0) {
            this.pixelWidth = aPixelWidth;
        }else{
            AxisPanelStyle parentStyle = getParentStyle();        
            if (parentStyle != null) {
                this.pixelWidth = parentStyle.pixelWidth + aPixelWidth; 
            }
        }
        
        if (aPixelHeight >=0 ) {
            this.pixelHeight = aPixelHeight;            
        }else{
            AxisPanelStyle parentStyle = getParentStyle();        
            if (parentStyle != null) {
                this.pixelHeight = parentStyle.pixelHeight + aPixelHeight;
            }
        }
        
        owner.setPixelSize(this.pixelWidth, this.pixelHeight);
        return this;
    }

    /**
     * Provide a pixel width/height percent relative to the parent style's
     *  pixel width/height
     */
    public AxisPanelStyle size(double aPixelWidthPercent, double aPixelHeightPercent) {
        this.pixelWidthPercent = aPixelWidthPercent;
        this.pixelHeightPercent = aPixelHeightPercent;
        
        AxisPanelStyle parentStyle = getParentStyle();        
        if (parentStyle != null) {
            this.pixelWidth = (int)(parentStyle.pixelWidth * this.pixelWidthPercent); 
            this.pixelHeight = (int)(parentStyle.pixelHeight * this.pixelHeightPercent);
        }
        
        owner.setPixelSize(this.pixelWidth, this.pixelHeight);
        return this;
    }

    /**
     * Provide a css width/height to be set directly.
     */    
    public AxisPanelStyle size(String cssWidth, String cssHeight) {
        width(cssWidth);
        height(cssHeight);
        return this;
    }
    
    /**
     * Provide a css width to be set directly.
     */
    public AxisPanelStyle width(String aCSSWidth) {
        this.cssWidth = aCSSWidth;
        owner.setWidth(this.cssWidth);
        return this;
    }
    
    /**
     * Provide a css height to be set directly.
     */
    public AxisPanelStyle height(String aCSSHeight) {
        this.cssHeight = aCSSHeight;
        owner.setHeight(this.cssHeight);
        return this;
    }
    
    /**
     * Give the spacing of owner panel's child cells.  Affects all 4 sides
     *  equally with the space pixels provided. 
     */
    public AxisPanelStyle space(int pixels) {
        this.pixelSpacing = pixels;
        owner.setSpacing(this.pixelSpacing);
        return this;
    }
    
    /**
     * Align the Panel.
     * Needs to be called before adding children to a Panel for best 
     * performance, but will work otherwise as well, it removes then re-addeds
     * all children after alignment is set.
     * 
     * @param x: -1, 0, 1 for horizontal left, center, right
     * @param y:  1, 0, -1 for vertical top, middle, bottom.
     *  
     * author Sony Mathew
     */
    public AxisPanelStyle align(int x, int y) {
        this.alignX = x;
        this.alignY = y;

        HorizontalAlignmentConstant hAlign = fromAlignX(this.alignX);        
        VerticalAlignmentConstant vAlign = fromAlignY(this.alignY);        
                
        if (owner instanceof HorizontalPanel) {
            HorizontalPanel howner = (HorizontalPanel)owner;
            howner.setHorizontalAlignment(hAlign);
            howner.setVerticalAlignment(vAlign);
        }else if (owner instanceof VerticalPanel) {
            VerticalPanel vowner = (VerticalPanel)owner;
            vowner.setHorizontalAlignment(hAlign);
            vowner.setVerticalAlignment(vAlign);
        }else{
            throw new IllegalStateException("Cannot align unrecongnized CellPanel type [" + owner.getClass() + "]");
        }

        //remove all children, then add them back.
        //
        int childCount = owner.getWidgetCount();
        if (childCount > 0) {
            List<Widget> children = new ArrayList<Widget>(childCount+1);
            for(int i=0;i<childCount;i++){
                children.add(owner.getWidget(i));
            }
            
            owner.clear();
            
            for(Widget child : children){
                owner.add(child);
            }            
        }
        
        return this;
    }
        
    public static HorizontalAlignmentConstant fromAlignX(int xAlign) {
        switch(xAlign) {
        case -1:
            return HasHorizontalAlignment.ALIGN_LEFT;
        case 0:
            return HasHorizontalAlignment.ALIGN_CENTER;
        case 1:
            return HasHorizontalAlignment.ALIGN_RIGHT;
        default:
            throw new IllegalStateException("Bad AlignX value provided [" + xAlign + "]");
        }        
    }
    
    public static VerticalAlignmentConstant fromAlignY(int yAlign) {
        switch(yAlign) {
        case 1:
            return HasVerticalAlignment.ALIGN_TOP;
        case 0:
            return HasVerticalAlignment.ALIGN_MIDDLE;
        case -1:
            return HasVerticalAlignment.ALIGN_BOTTOM;            
        default:
            throw new IllegalStateException("Bad AlignY value provided [" + yAlign + "]");
        }        
    }
}
